/*
 * Copyright © 2018 www.noark.xyz All Rights Reserved.
 *
 * 感谢您选择Noark框架，希望我们的努力能为您提供一个简单、易用、稳定的服务器端框架 ！
 * 除非符合Noark许可协议，否则不得使用该文件，您可以下载许可协议文件：
 *
 *        http://www.noark.xyz/LICENSE
 *
 * 1.未经许可，任何公司及个人不得以任何方式或理由对本框架进行修改、使用和传播;
 * 2.禁止在本项目或任何子项目的基础上发展任何派生版本、修改版本或第三方版本;
 * 3.无论你对源代码做出任何修改和改进，版权都归Noark研发团队所有，我们保留所有权利;
 * 4.凡侵犯Noark版权等知识产权的，必依法追究其法律责任，特此郑重法律声明！
 */
package xyz.noark.core.util;

import xyz.noark.core.exception.ServerBootstrapException;

import java.net.SocketException;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * TraceId工具类
 *
 * @author 小流氓[176543888@qq.com]
 * @since 3.4.9
 */
public class TracerUtils {
    /**
     * 56个字符=大写字母+小写字母+数字
     */
    private static final char[] ENCODING = {
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
            'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R',
            'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',

            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
            'i', 'j', 'k', 'm', 'n', 'p', 'q', 'r',
            's', 't', 'u', 'v', 'w', 'x', 'y', 'z',

            '2', '3', '4', '5', '6', '7', '8', '9'
    };

    /**
     * 编码长度
     */
    private static final int ENCODING_LENGTH = ENCODING.length;
    /**
     * IP地址，以56个字符编码后长度为6位
     */
    private static final long IP = initLocalHostAddress();
    /**
     * PID，最大值：4194304，以56个字符编码后长度为4位
     * <pre>
     * $ cat /proc/sys/kernel/pid_max
     * 4194304
     * </pre>
     */
    private static final long PID = SystemUtils.getPid();

    /**
     * 自旋因子:每次加1，最大值为3135
     */
    private static final AtomicInteger sequence = new AtomicInteger(1);
    /**
     * 最大自旋因子值，就是字符编码的平方-1
     */
    private static final long MAX_SEQUENCE = ENCODING_LENGTH * ENCODING_LENGTH - 1;

    /**
     * 生成一个全服唯一的TraceId.
     * <p>
     * 编码规则：IP（6位）+ PID（4位）+ 时间（8位）+ 自旋因子（2位）<br>
     * 每秒最大产出 313_5000 个TraceId<br>
     * 会不会重复？不超出产速上限就不会重，超出就会重复，但不会报错，本身这个重复了问题也不大
     *
     * @return TraceId字符串
     */
    public static String generateId() {
        final StringBuilder sb = new StringBuilder(20);

        // 1. 开始编码IP
        encodeLong(IP, sb, 6);
        // 2. 开始编码PID
        encodeLong(PID, sb, 4);
        // 3. 开始编码时间戳
        encodeLong(System.currentTimeMillis(), sb, 8);
        // 4. 自旋因子
        encodeLong(nextSequence(), sb, 2);

        // 最终的结果
        return sb.toString();
    }

    /**
     * 编码每个节点的数字，如果编码后的长度不足，以0补全
     *
     * @param value  节点的数字
     * @param sb     字符串编码区
     * @param length 固定长度
     */
    private static void encodeLong(long value, StringBuilder sb, int length) {
        final int max = sb.length() + length;
        while (value > 0) {
            sb.append(ENCODING[(int) (value % ENCODING_LENGTH)]);
            value /= ENCODING_LENGTH;
        }
        // 位置不足后面补0，主要是想让大家长得一样长
        for (int i = sb.length(); i < max; i++) {
            sb.append('0');
        }
    }

    /**
     * 获取下一个自旋因子值
     *
     * @return 自旋因子值
     */
    private static long nextSequence() {
        // 需要对自旋因子的符号进行处理一下
        return UnsignedUtils.toUnsigned(sequence.getAndIncrement()) % MAX_SEQUENCE;
    }

    /**
     * 初始化本地IP地址
     *
     * @return 本地IP地址
     */
    private static long initLocalHostAddress() {
        try {
            return IpUtils.ipToLong(IpUtils.getLocalAddress());
        } catch (SocketException e) {
            throw new ServerBootstrapException("无法获取内网IP地址", e);
        }
    }
}
